| 1 | // Copyright (C) 2021 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include <private/scatterchartitem_p.h> | 
| 5 | #include <QtCharts/QScatterSeries> | 
| 6 | #include <private/qscatterseries_p.h> | 
| 7 | #include <private/chartpresenter_p.h> | 
| 8 | #include <private/abstractdomain_p.h> | 
| 9 | #include <QtCharts/QChart> | 
| 10 | #include <QtGui/QPainter> | 
| 11 | #include <QtWidgets/QGraphicsScene> | 
| 12 | #include <QtCore/QDebug> | 
| 13 | #include <QtWidgets/QGraphicsSceneMouseEvent> | 
| 14 |  | 
| 15 | QT_BEGIN_NAMESPACE | 
| 16 |  | 
| 17 | namespace { | 
| 18 | constexpr short STAR_SPIKES = 5; | 
| 19 | } | 
| 20 |  | 
| 21 | ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem *item) | 
| 22 |     : XYChart(series,item), | 
| 23 |       m_series(series), | 
| 24 |       m_items(this), | 
| 25 |       m_visible(true), | 
| 26 |       m_markerShape(QScatterSeries::MarkerShapeRectangle), | 
| 27 |       m_pointsVisible(true), | 
| 28 |       m_pointLabelsVisible(false), | 
| 29 |       m_markerSize(series->markerSize()), | 
| 30 |       m_pointLabelsFormat(series->pointLabelsFormat()), | 
| 31 |       m_pointLabelsFont(series->pointLabelsFont()), | 
| 32 |       m_pointLabelsColor(series->pointLabelsColor()), | 
| 33 |       m_pointLabelsClipping(true), | 
| 34 |       m_lastHoveredPoint(QPointF(qQNaN(), qQNaN())), | 
| 35 |       m_mousePressed(false) | 
| 36 | { | 
| 37 |     connect(sender: series->d_func(), signal: &QXYSeriesPrivate::seriesUpdated, | 
| 38 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 39 |     connect(sender: series, signal: &QXYSeries::lightMarkerChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 40 |     connect(sender: series, signal: &QXYSeries::selectedLightMarkerChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 41 |     connect(sender: series, signal: &QXYSeries::markerSizeChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 42 |     connect(sender: series, signal: &QXYSeries::visibleChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 43 |     connect(sender: series, signal: &QXYSeries::opacityChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 44 |     connect(sender: series, signal: &QXYSeries::pointLabelsFormatChanged, | 
| 45 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 46 |     connect(sender: series, signal: &QXYSeries::pointLabelsVisibilityChanged, | 
| 47 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 48 |     connect(sender: series, signal: &QXYSeries::pointLabelsFontChanged, | 
| 49 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 50 |     connect(sender: series, signal: &QXYSeries::pointLabelsColorChanged, | 
| 51 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 52 |     connect(sender: series, signal: &QXYSeries::pointLabelsClippingChanged, | 
| 53 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 54 |     connect(sender: series, signal: &QXYSeries::selectedColorChanged, | 
| 55 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 56 |     connect(sender: series, signal: &QXYSeries::selectedPointsChanged, | 
| 57 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 58 |     connect(sender: series, signal: &QScatterSeries::pointsConfigurationChanged, | 
| 59 |             context: this, slot: &ScatterChartItem::handleSeriesUpdated); | 
| 60 |  | 
| 61 |     setZValue(ChartPresenter::ScatterSeriesZValue); | 
| 62 |     setFlags(QGraphicsItem::ItemClipsChildrenToShape | QGraphicsItem::ItemIsSelectable); | 
| 63 |  | 
| 64 |     handleSeriesUpdated(); | 
| 65 |  | 
| 66 |     m_items.setHandlesChildEvents(false); | 
| 67 | } | 
| 68 |  | 
| 69 | QRectF ScatterChartItem::boundingRect() const | 
| 70 | { | 
| 71 |     return m_rect; | 
| 72 | } | 
| 73 |  | 
| 74 | void ScatterChartItem::createPoints(int count) | 
| 75 | { | 
| 76 |     for (int i = 0; i < count; ++i) { | 
| 77 |  | 
| 78 |         QGraphicsItem *item = 0; | 
| 79 |  | 
| 80 |         switch (m_markerShape) { | 
| 81 |         case QScatterSeries::MarkerShapeCircle: { | 
| 82 |             item = new ChartMarker<QGraphicsEllipseItem>(0, 0, m_markerSize, m_markerSize, this); | 
| 83 |             break; | 
| 84 |         } | 
| 85 |         case QScatterSeries::MarkerShapeRectangle: { | 
| 86 |             item = new ChartMarker<QGraphicsRectItem>(0, 0, m_markerSize, m_markerSize, this); | 
| 87 |             break; | 
| 88 |         } | 
| 89 |         case QScatterSeries::MarkerShapeRotatedRectangle: { | 
| 90 |             item = new RotatedRectangleMarker(0, 0, m_markerSize, m_markerSize, this); | 
| 91 |             break; | 
| 92 |         } | 
| 93 |         case QScatterSeries::MarkerShapeTriangle: { | 
| 94 |             item = new TriangleMarker(0, 0, m_markerSize, m_markerSize, this); | 
| 95 |             break; | 
| 96 |         } | 
| 97 |         case QScatterSeries::MarkerShapeStar: { | 
| 98 |             item = new StarMarker(0, 0, m_markerSize, m_markerSize, this); | 
| 99 |             break; | 
| 100 |         } | 
| 101 |         case QScatterSeries::MarkerShapePentagon: { | 
| 102 |             item = new PentagonMarker(0, 0, m_markerSize, m_markerSize, this); | 
| 103 |             break; | 
| 104 |         } | 
| 105 |         default: | 
| 106 |             qWarning() << "Unsupported marker type" ; | 
| 107 |             break; | 
| 108 |         } | 
| 109 |  | 
| 110 |         m_items.addToGroup(item); | 
| 111 |     } | 
| 112 | } | 
| 113 |  | 
| 114 | void ScatterChartItem::deletePoints(int count) | 
| 115 | { | 
| 116 |     QList<QGraphicsItem *> items = m_items.childItems(); | 
| 117 |  | 
| 118 |     for (int i = 0; i < count; ++i) { | 
| 119 |         QGraphicsItem *item = items.takeLast(); | 
| 120 |         m_markerMap.remove(key: item); | 
| 121 |         delete(item); | 
| 122 |     } | 
| 123 | } | 
| 124 |  | 
| 125 | void ScatterChartItem::resizeMarker(QGraphicsItem *marker, const int size) | 
| 126 | { | 
| 127 |     switch (m_markerShape) { | 
| 128 |     case QScatterSeries::MarkerShapeCircle: { | 
| 129 |         QGraphicsEllipseItem *item = static_cast<QGraphicsEllipseItem *>(marker); | 
| 130 |         item->setRect(ax: 0, ay: 0, w: size, h: size); | 
| 131 |         break; | 
| 132 |     } | 
| 133 |     case QScatterSeries::MarkerShapeRectangle: { | 
| 134 |         QGraphicsRectItem *item = static_cast<QGraphicsRectItem *>(marker); | 
| 135 |         item->setRect(ax: 0, ay: 0, w: size, h: size); | 
| 136 |         break; | 
| 137 |     } | 
| 138 |     case QScatterSeries::MarkerShapeRotatedRectangle: { | 
| 139 |         QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); | 
| 140 |         item->setPolygon(RotatedRectangleMarker::polygon(x: 0, y: 0, w: size, h: size)); | 
| 141 |         break; | 
| 142 |     } | 
| 143 |     case QScatterSeries::MarkerShapeTriangle: { | 
| 144 |         QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); | 
| 145 |         item->setPolygon(TriangleMarker::polygon(x: 0, y: 0, w: size, h: size)); | 
| 146 |         break; | 
| 147 |     } | 
| 148 |     case QScatterSeries::MarkerShapeStar: { | 
| 149 |         QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); | 
| 150 |         item->setPolygon(StarMarker::polygon(x: 0, y: 0, w: size, h: size)); | 
| 151 |         break; | 
| 152 |     } | 
| 153 |     case QScatterSeries::MarkerShapePentagon: { | 
| 154 |         QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); | 
| 155 |         item->setPolygon(PentagonMarker::polygon(x: 0, y: 0, w: size, h: size)); | 
| 156 |         break; | 
| 157 |     } | 
| 158 |     default: | 
| 159 |         qWarning() << "Unsupported marker type" ; | 
| 160 |         break; | 
| 161 |     } | 
| 162 | } | 
| 163 |  | 
| 164 | void ScatterChartItem::markerSelected(QGraphicsItem *marker) | 
| 165 | { | 
| 166 |     emit XYChart::clicked(point: m_markerMap[marker]); | 
| 167 | } | 
| 168 |  | 
| 169 | void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state) | 
| 170 | { | 
| 171 |     emit XYChart::hovered(point: m_markerMap[marker], state); | 
| 172 | } | 
| 173 |  | 
| 174 | void ScatterChartItem::markerPressed(QGraphicsItem *marker) | 
| 175 | { | 
| 176 |     emit XYChart::pressed(point: m_markerMap[marker]); | 
| 177 | } | 
| 178 |  | 
| 179 | void ScatterChartItem::markerReleased(QGraphicsItem *marker) | 
| 180 | { | 
| 181 |     emit XYChart::released(point: m_markerMap[marker]); | 
| 182 | } | 
| 183 |  | 
| 184 | void ScatterChartItem::markerDoubleClicked(QGraphicsItem *marker) | 
| 185 | { | 
| 186 |     emit XYChart::doubleClicked(point: m_markerMap[marker]); | 
| 187 | } | 
| 188 |  | 
| 189 | void ScatterChartItem::updateGeometry() | 
| 190 | { | 
| 191 |     if (m_series->useOpenGL()) { | 
| 192 |         if (m_items.childItems().size()) | 
| 193 |             deletePoints(count: m_items.childItems().size()); | 
| 194 |         if (!m_rect.isEmpty()) { | 
| 195 |             prepareGeometryChange(); | 
| 196 |             // Changed signal seems to trigger even with empty region | 
| 197 |             m_rect = QRectF(); | 
| 198 |         } | 
| 199 |         update(); | 
| 200 |         return; | 
| 201 |     } | 
| 202 |  | 
| 203 |     const QList<QPointF> &points = geometryPoints(); | 
| 204 |  | 
| 205 |     if (points.size() == 0) { | 
| 206 |         deletePoints(count: m_items.childItems().size()); | 
| 207 |         return; | 
| 208 |     } | 
| 209 |  | 
| 210 |     int diff = m_items.childItems().size() - points.size(); | 
| 211 |  | 
| 212 |     if (diff > 0) | 
| 213 |         deletePoints(count: diff); | 
| 214 |     else if (diff < 0) | 
| 215 |         createPoints(count: -diff); | 
| 216 |  | 
| 217 |     if (diff != 0) | 
| 218 |         handleSeriesUpdated(); | 
| 219 |  | 
| 220 |     QList<QGraphicsItem *> items = m_items.childItems(); | 
| 221 |  | 
| 222 |     QRectF clipRect(QPointF(0,0),domain()->size()); | 
| 223 |  | 
| 224 |     // Only zoom in if the clipRect fits inside int limits. QWidget::update() uses | 
| 225 |     // a region that has to be compatible with QRect. | 
| 226 |     if (clipRect.height() <= INT_MAX | 
| 227 |             && clipRect.width() <= INT_MAX) { | 
| 228 |         const QList<bool> offGridStatus = offGridStatusVector(); | 
| 229 |         const int seriesLastIndex = m_series->count() - 1; | 
| 230 |  | 
| 231 |         for (int i = 0; i < points.size(); i++) { | 
| 232 |             QAbstractGraphicsShapeItem *item = | 
| 233 |                     static_cast<QAbstractGraphicsShapeItem *>(items.at(i)); | 
| 234 |             const QPointF &point = points.at(i); | 
| 235 |  | 
| 236 |             if (m_pointsConfiguration.contains(key: i)) { | 
| 237 |                 const auto &conf = m_pointsConfiguration[i]; | 
| 238 |                 if (conf.contains(key: QXYSeries::PointConfiguration::Size)) | 
| 239 |                     resizeMarker( | 
| 240 |                             marker: item, | 
| 241 |                             size: m_pointsConfiguration[i][QXYSeries::PointConfiguration::Size].toReal()); | 
| 242 |             } | 
| 243 |  | 
| 244 |             const QRectF &rect = item->boundingRect(); | 
| 245 |             // During remove animation series may have different number of points, | 
| 246 |             // so ensure we don't go over the index. Animation handling itself ensures that | 
| 247 |             // if there is actually no points in the series, then it won't generate a fake point, | 
| 248 |             // so we can be assured there is always at least one point in m_series here. | 
| 249 |             // Note that marker map values can be technically incorrect during the animation, | 
| 250 |             // if it was caused by an insert, but this shouldn't be a problem as the points are | 
| 251 |             // fake anyway. After remove animation stops, geometry is updated to correct one. | 
| 252 |             m_markerMap[item] = m_series->at(index: qMin(a: seriesLastIndex, b: i)); | 
| 253 |             QPointF position; | 
| 254 |             position.setX(point.x() - rect.width() / 2); | 
| 255 |             position.setY(point.y() - rect.height() / 2); | 
| 256 |             item->setPos(position); | 
| 257 |  | 
| 258 |             if (!m_visible || offGridStatus.at(i)) { | 
| 259 |                 item->setVisible(false); | 
| 260 |             } else { | 
| 261 |                 bool drawPoint = m_pointsVisible; | 
| 262 |                 if (m_pointsConfiguration.contains(key: i)) { | 
| 263 |                     const auto &conf = m_pointsConfiguration[i]; | 
| 264 |  | 
| 265 |                     if (conf.contains(key: QXYSeries::PointConfiguration::Visibility)) { | 
| 266 |                         drawPoint | 
| 267 |                                 = m_pointsConfiguration[i][QXYSeries::PointConfiguration::Visibility] | 
| 268 |                                 .toBool(); | 
| 269 |                     } | 
| 270 |  | 
| 271 |                     if (drawPoint && conf.contains(key: QXYSeries::PointConfiguration::Color)) { | 
| 272 |                         item->setBrush( | 
| 273 |                                 m_pointsConfiguration[i][QXYSeries::PointConfiguration::Color] | 
| 274 |                                         .value<QColor>()); | 
| 275 |                     } | 
| 276 |                 } | 
| 277 |  | 
| 278 |                 if (m_series->isPointSelected(index: i)) { | 
| 279 |                     drawPoint = m_series->selectedLightMarker().isNull(); | 
| 280 |                     if (drawPoint && m_selectedColor.isValid()) | 
| 281 |                         item->setBrush(m_selectedColor); | 
| 282 |                 } | 
| 283 |  | 
| 284 |                 item->setVisible(drawPoint); | 
| 285 |             } | 
| 286 |         } | 
| 287 |  | 
| 288 |         prepareGeometryChange(); | 
| 289 |         m_rect = clipRect; | 
| 290 |     } | 
| 291 | } | 
| 292 |  | 
| 293 | void ScatterChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event) | 
| 294 | { | 
| 295 |     QPointF matchedP = matchForLightMarker(eventPos: event->pos()); | 
| 296 |     if (!qIsNaN(d: matchedP.x())) { | 
| 297 |         emit XYChart::pressed(point: matchedP); | 
| 298 |         m_lastMousePos = event->pos(); | 
| 299 |         m_mousePressed = true; | 
| 300 |     } else { | 
| 301 |         event->ignore(); | 
| 302 |     } | 
| 303 |  | 
| 304 |     QGraphicsItem::mousePressEvent(event); | 
| 305 | } | 
| 306 |  | 
| 307 | void ScatterChartItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) | 
| 308 | { | 
| 309 |     QPointF matchedP = matchForLightMarker(eventPos: event->pos()); | 
| 310 |     if (!qIsNaN(d: matchedP.x())) { | 
| 311 |         if (matchedP != m_lastHoveredPoint) { | 
| 312 |             if (!qIsNaN(d: m_lastHoveredPoint.x())) | 
| 313 |                 emit XYChart::hovered(point: m_lastHoveredPoint, state: false); | 
| 314 |  | 
| 315 |             m_lastHoveredPoint = matchedP; | 
| 316 |             emit XYChart::hovered(point: matchedP, state: true); | 
| 317 |         } | 
| 318 |     } else if (!qIsNaN(d: m_lastHoveredPoint.x())) { | 
| 319 |         emit XYChart::hovered(point: m_lastHoveredPoint, state: false); | 
| 320 |         m_lastHoveredPoint = QPointF(qQNaN(), qQNaN()); | 
| 321 |     } | 
| 322 |  | 
| 323 |     QGraphicsItem::hoverMoveEvent(event); | 
| 324 | } | 
| 325 |  | 
| 326 | void ScatterChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | 
| 327 | { | 
| 328 |     QPointF result; | 
| 329 |     QPointF matchedP = matchForLightMarker(eventPos: m_lastMousePos); | 
| 330 |     if (!qIsNaN(d: matchedP.x()) && m_mousePressed) { | 
| 331 |         result = matchedP; | 
| 332 |         emit XYChart::released(point: result); | 
| 333 |         emit XYChart::clicked(point: result); | 
| 334 |     } | 
| 335 |  | 
| 336 |     m_mousePressed = false; | 
| 337 |     QGraphicsItem::mouseReleaseEvent(event); | 
| 338 | } | 
| 339 |  | 
| 340 | void ScatterChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) | 
| 341 | { | 
| 342 |     QPointF matchedP = matchForLightMarker(eventPos: event->pos()); | 
| 343 |     if (!qIsNaN(d: matchedP.x())) | 
| 344 |         emit XYChart::doubleClicked(point: matchedP); | 
| 345 |     else | 
| 346 |         emit XYChart::doubleClicked(point: domain()->calculateDomainPoint(point: m_lastMousePos)); | 
| 347 |  | 
| 348 |     QGraphicsItem::mouseDoubleClickEvent(event); | 
| 349 | } | 
| 350 |  | 
| 351 | void ScatterChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | 
| 352 | { | 
| 353 |     Q_UNUSED(option); | 
| 354 |     Q_UNUSED(widget); | 
| 355 |  | 
| 356 |     if (m_series->useOpenGL()) | 
| 357 |         return; | 
| 358 |  | 
| 359 |     QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); | 
| 360 |  | 
| 361 |     painter->save(); | 
| 362 |     painter->setClipRect(clipRect); | 
| 363 |  | 
| 364 |     // Draw markers if a marker or marker for selected points only has been | 
| 365 |     // set (set to QImage() to disable) | 
| 366 |     if (!m_series->lightMarker().isNull() || !m_series->selectedLightMarker().isNull()) { | 
| 367 |         const QImage &marker = m_series->lightMarker(); | 
| 368 |         const QImage &selectedMarker = m_series->selectedLightMarker(); | 
| 369 |         qreal markerHalfSize = m_markerSize / 2.0; | 
| 370 |  | 
| 371 |         for (int i = 0; i < m_points.size(); ++i) { | 
| 372 |             // Documentation of light markers says that points visibility and | 
| 373 |             // light markers are independent features. Therefore m_pointsVisible | 
| 374 |             // is not used here as light markers are drawn if lightMarker is not null. | 
| 375 |             // However points visibility configuration can be still used here. | 
| 376 |             bool drawPoint = !m_series->lightMarker().isNull(); | 
| 377 |             if (m_pointsConfiguration.contains(key: i)) { | 
| 378 |                 const auto &conf = m_pointsConfiguration[i]; | 
| 379 |  | 
| 380 |                 if (conf.contains(key: QXYSeries::PointConfiguration::Visibility)) { | 
| 381 |                     drawPoint = m_pointsConfiguration[i][QXYSeries::PointConfiguration::Visibility] | 
| 382 |                                         .toBool(); | 
| 383 |                 } | 
| 384 |             } | 
| 385 |  | 
| 386 |             bool drawSelectedPoint = false; | 
| 387 |             if (m_series->isPointSelected(index: i)) { | 
| 388 |                 drawPoint = true; | 
| 389 |                 drawSelectedPoint = !selectedMarker.isNull(); | 
| 390 |             } | 
| 391 |             if (drawPoint) { | 
| 392 |                 const QRectF rect(m_points[i].x() - markerHalfSize, | 
| 393 |                                   m_points[i].y() - markerHalfSize, | 
| 394 |                                   m_markerSize, m_markerSize); | 
| 395 |                 painter->drawImage(r: rect, image: drawSelectedPoint ? selectedMarker : marker); | 
| 396 |             } | 
| 397 |         } | 
| 398 |     } | 
| 399 |  | 
| 400 |     if (m_series->bestFitLineVisible()) | 
| 401 |         m_series->d_func()->drawBestFitLine(painter, clipRect); | 
| 402 |  | 
| 403 |     m_series->d_func()->drawPointLabels(painter, allPoints: m_points, offset: m_series->markerSize() / 2 + m_series->pen().width()); | 
| 404 |  | 
| 405 |     painter->restore(); | 
| 406 | } | 
| 407 |  | 
| 408 | void ScatterChartItem::setPen(const QPen &pen) | 
| 409 | { | 
| 410 |     QPen penToUse(pen); | 
| 411 |     if (!m_series->lightMarker().isNull()) | 
| 412 |         penToUse.setColor(Qt::transparent); | 
| 413 |  | 
| 414 |     const auto &items = m_items.childItems(); | 
| 415 |     for (auto item : items) | 
| 416 |         static_cast<QAbstractGraphicsShapeItem*>(item)->setPen(penToUse); | 
| 417 | } | 
| 418 |  | 
| 419 | void ScatterChartItem::setBrush(const QBrush &brush) | 
| 420 | { | 
| 421 |     const auto &items = m_items.childItems(); | 
| 422 |     for (auto item : items) { | 
| 423 |         if (m_series->lightMarker().isNull()) { | 
| 424 |             if (m_markerMap.contains(key: item)) { | 
| 425 |                 auto index = m_series->points().indexOf(t: m_markerMap[item]); | 
| 426 |                 if (m_selectedPoints.contains(t: index) && m_selectedColor.isValid()) { | 
| 427 |                     static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(m_selectedColor); | 
| 428 |                 } else { | 
| 429 |                     bool useBrush = true; | 
| 430 |                     if (m_pointsConfiguration.contains(key: index)) { | 
| 431 |                         const auto &conf = m_pointsConfiguration[index]; | 
| 432 |                         if (conf.contains(key: QXYSeries::PointConfiguration::Color)) | 
| 433 |                             useBrush = false; | 
| 434 |                     } | 
| 435 |  | 
| 436 |                     if (useBrush) | 
| 437 |                         static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(brush); | 
| 438 |                 } | 
| 439 |             } else { | 
| 440 |                 static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(brush); | 
| 441 |             } | 
| 442 |         } else { | 
| 443 |             QBrush brushToUse; | 
| 444 |             brushToUse.setColor(Qt::transparent); | 
| 445 |             static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(brushToUse); | 
| 446 |         } | 
| 447 |     } | 
| 448 | } | 
| 449 |  | 
| 450 | void ScatterChartItem::handleSeriesUpdated() | 
| 451 | { | 
| 452 |     if (m_series->useOpenGL()) { | 
| 453 |         if ((m_series->isVisible() != m_visible)) { | 
| 454 |             m_visible = m_series->isVisible(); | 
| 455 |             refreshGlChart(); | 
| 456 |         } | 
| 457 |         return; | 
| 458 |     } | 
| 459 |  | 
| 460 |     int count = m_items.childItems().size(); | 
| 461 |     if (count == 0) | 
| 462 |         return; | 
| 463 |  | 
| 464 |     const bool pointsConfigurationDirty = | 
| 465 |             m_series->pointsConfiguration() != m_pointsConfiguration; | 
| 466 |  | 
| 467 |     bool recreate = m_visible != m_series->isVisible() | 
| 468 |                     || m_pointsVisible != m_series->pointsVisible() | 
| 469 |                     || m_markerSize != m_series->markerSize() | 
| 470 |                     || m_markerShape != m_series->markerShape() | 
| 471 |                     || m_selectedColor != m_series->selectedColor() | 
| 472 |                     || m_selectedPoints != m_series->selectedPoints() | 
| 473 |                     || pointsConfigurationDirty; | 
| 474 |     m_visible = m_series->isVisible(); | 
| 475 |     m_markerSize = m_series->markerSize(); | 
| 476 |     m_markerShape = m_series->markerShape(); | 
| 477 |     setVisible(m_visible); | 
| 478 |     setOpacity(m_series->opacity()); | 
| 479 |     m_pointsVisible = m_series->pointsVisible(); | 
| 480 |     m_pointLabelsFormat = m_series->pointLabelsFormat(); | 
| 481 |     m_pointLabelsVisible = m_series->pointLabelsVisible(); | 
| 482 |     m_pointLabelsFont = m_series->pointLabelsFont(); | 
| 483 |     m_pointLabelsColor = m_series->pointLabelsColor(); | 
| 484 |     m_selectedColor = m_series->selectedColor(); | 
| 485 |     m_selectedPoints = m_series->selectedPoints(); | 
| 486 |     m_pointsConfiguration = m_series->pointsConfiguration(); | 
| 487 |     bool labelClippingChanged = m_pointLabelsClipping != m_series->pointLabelsClipping(); | 
| 488 |     m_pointLabelsClipping = m_series->pointLabelsClipping(); | 
| 489 |  | 
| 490 |     if (recreate) { | 
| 491 |         deletePoints(count); | 
| 492 |         createPoints(count); | 
| 493 |  | 
| 494 |         // Updating geometry is now safe, because it won't call handleSeriesUpdated unless it | 
| 495 |         // creates/deletes points | 
| 496 |         updateGeometry(); | 
| 497 |     } | 
| 498 |  | 
| 499 |     // Only accept hover events when light/selection markers are in use so we don't unnecessarily | 
| 500 |     // eat the events in the regular case | 
| 501 |     setAcceptHoverEvents(!(m_series->lightMarker().isNull() | 
| 502 |                            && (m_series->selectedLightMarker().isNull() | 
| 503 |                                || m_series->selectedPoints().isEmpty()))); | 
| 504 |  | 
| 505 |     setPen(m_series->pen()); | 
| 506 |     setBrush(m_series->brush()); | 
| 507 |     // Update whole chart in case label clipping changed as labels can be outside series area | 
| 508 |     if (labelClippingChanged) | 
| 509 |         m_series->chart()->update(); | 
| 510 |     else | 
| 511 |         update(); | 
| 512 | } | 
| 513 |  | 
| 514 | void ScatterChartItem::handleMarkerMouseReleaseEvent(QGraphicsItem *item) | 
| 515 | { | 
| 516 |     markerReleased(marker: item); | 
| 517 |     if (mousePressed()) | 
| 518 |         markerSelected(marker: item); | 
| 519 |     setMousePressed(false); | 
| 520 | } | 
| 521 |  | 
| 522 | template<class T> | 
| 523 | ChartMarker<T>::ChartMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) | 
| 524 |     : T(x, y, w, h, parent) | 
| 525 |     , m_parent(parent) | 
| 526 | { | 
| 527 |     T::setAcceptHoverEvents(true); | 
| 528 |     T::setFlag(QGraphicsItem::ItemIsSelectable); | 
| 529 | } | 
| 530 |  | 
| 531 | template<class T> | 
| 532 | ChartMarker<T>::ChartMarker(ScatterChartItem *parent) | 
| 533 |     : T(parent) | 
| 534 |     , m_parent(parent) | 
| 535 | { | 
| 536 |     T::setAcceptHoverEvents(true); | 
| 537 |     T::setFlag(QGraphicsItem::ItemIsSelectable); | 
| 538 | } | 
| 539 |  | 
| 540 | template<class T> | 
| 541 | void ChartMarker<T>::mousePressEvent(QGraphicsSceneMouseEvent *event) | 
| 542 | { | 
| 543 |     T::mousePressEvent(event); | 
| 544 |     m_parent->markerPressed(marker: this); | 
| 545 |     m_parent->setMousePressed(); | 
| 546 | } | 
| 547 |  | 
| 548 | template<class T> | 
| 549 | void ChartMarker<T>::hoverEnterEvent(QGraphicsSceneHoverEvent *event) | 
| 550 | { | 
| 551 |     T::hoverEnterEvent(event); | 
| 552 |     m_parent->markerHovered(marker: this, state: true); | 
| 553 | } | 
| 554 |  | 
| 555 | template<class T> | 
| 556 | void ChartMarker<T>::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) | 
| 557 | { | 
| 558 |     T::hoverLeaveEvent(event); | 
| 559 |     m_parent->markerHovered(marker: this, state: false); | 
| 560 | } | 
| 561 |  | 
| 562 | template<class T> | 
| 563 | void ChartMarker<T>::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | 
| 564 | { | 
| 565 |     T::mouseReleaseEvent(event); | 
| 566 |     m_parent->handleMarkerMouseReleaseEvent(item: this); | 
| 567 | } | 
| 568 |  | 
| 569 | template<class T> | 
| 570 | void ChartMarker<T>::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) | 
| 571 | { | 
| 572 |     T::mouseDoubleClickEvent(event); | 
| 573 |     m_parent->markerDoubleClicked(marker: this); | 
| 574 | } | 
| 575 |  | 
| 576 | RotatedRectangleMarker::RotatedRectangleMarker(qreal x, qreal y, qreal w, qreal h, | 
| 577 |                                                ScatterChartItem *parent) | 
| 578 |     : ChartMarker<QGraphicsPolygonItem>(parent) | 
| 579 | { | 
| 580 |     setPolygon(RotatedRectangleMarker::polygon(x, y, w, h)); | 
| 581 | } | 
| 582 |  | 
| 583 | QPolygonF RotatedRectangleMarker::polygon(qreal x, qreal y, qreal w, qreal h) | 
| 584 | { | 
| 585 |     QPolygonF rotatedRectPoly; | 
| 586 |     rotatedRectPoly << QPointF(x, y + h / 2.0); | 
| 587 |     rotatedRectPoly << QPointF(x + w / 2.0, y + h); | 
| 588 |     rotatedRectPoly << QPointF(x + w, y + h / 2.0); | 
| 589 |     rotatedRectPoly << QPointF(x + w / 2.0, y); | 
| 590 |  | 
| 591 |     return rotatedRectPoly; | 
| 592 | } | 
| 593 |  | 
| 594 | TriangleMarker::TriangleMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) | 
| 595 |     : ChartMarker<QGraphicsPolygonItem>(parent) | 
| 596 | { | 
| 597 |     setPolygon(TriangleMarker::polygon(x, y, w, h)); | 
| 598 | } | 
| 599 |  | 
| 600 | QPolygonF TriangleMarker::polygon(qreal x, qreal y, qreal w, qreal h) | 
| 601 | { | 
| 602 |     QPolygonF trianglePoly; | 
| 603 |     trianglePoly << QPointF(x, y + h); | 
| 604 |     trianglePoly << QPointF(x + w, y + h); | 
| 605 |     trianglePoly << QPointF(x + w / 2.0, y); | 
| 606 |  | 
| 607 |     return trianglePoly; | 
| 608 | } | 
| 609 |  | 
| 610 | StarMarker::StarMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) | 
| 611 |     : ChartMarker<QGraphicsPolygonItem>(parent) | 
| 612 | { | 
| 613 |     setPolygon(StarMarker::polygon(x, y, w, h)); | 
| 614 | } | 
| 615 |  | 
| 616 | QPolygonF StarMarker::polygon(qreal x, qreal y, qreal w, qreal h) | 
| 617 | { | 
| 618 |     QPolygonF starPoly; | 
| 619 |  | 
| 620 |     constexpr qreal step = M_PI / STAR_SPIKES; | 
| 621 |     const qreal radius = w / 2.0; | 
| 622 |     const qreal innerRadius = radius * 0.5; | 
| 623 |     const QPointF ¢er = QPointF(x + w / 2.0, y + h / 2.0); | 
| 624 |     qreal rot = M_PI / 2 * 3; | 
| 625 |  | 
| 626 |     for (int i = 0; i < STAR_SPIKES; ++i) { | 
| 627 |         starPoly << QPointF(center.x() + std::cos(x: rot) * radius, | 
| 628 |                             center.y() + std::sin(x: rot) * radius); | 
| 629 |         rot += step; | 
| 630 |  | 
| 631 |         starPoly << QPointF(center.x() + std::cos(x: rot) * innerRadius, | 
| 632 |                             center.y() + std::sin(x: rot) * innerRadius); | 
| 633 |         rot += step; | 
| 634 |     } | 
| 635 |  | 
| 636 |     return starPoly; | 
| 637 | } | 
| 638 |  | 
| 639 | PentagonMarker::PentagonMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) | 
| 640 |     : ChartMarker<QGraphicsPolygonItem>(parent) | 
| 641 | { | 
| 642 |     setPolygon(PentagonMarker::polygon(x, y, w, h)); | 
| 643 | } | 
| 644 |  | 
| 645 | QPolygonF PentagonMarker::polygon(qreal x, qreal y, qreal w, qreal h) | 
| 646 | { | 
| 647 |     QPolygonF pentagonPoly; | 
| 648 |  | 
| 649 |     constexpr qreal step = 2 * M_PI / 5; | 
| 650 |     const qreal radius = w / 2.0; | 
| 651 |     const QPointF ¢er = QPointF(x + w / 2.0, y + h / 2.0); | 
| 652 |     qreal rot = M_PI / 2 * 3; | 
| 653 |  | 
| 654 |     for (int i = 0; i < 5; ++i) { | 
| 655 |         pentagonPoly << QPointF(center.x() + std::cos(x: rot) * radius, | 
| 656 |                                 center.y() + std::sin(x: rot) * radius); | 
| 657 |         rot += step; | 
| 658 |     } | 
| 659 |  | 
| 660 |     return pentagonPoly; | 
| 661 | } | 
| 662 |  | 
| 663 | QT_END_NAMESPACE | 
| 664 |  | 
| 665 | #include "moc_scatterchartitem_p.cpp" | 
| 666 |  |